import os
import sys

from utils.basic import get_path,read_yaml_file
import pathlib
from loguru import logger
from utils.py_object import cache
from selenium.webdriver.common.by import By


# 专门做setup和teardown的操作
def hooks(kwargs,api,mysql,type='setup'):
    """
    用于执行setup和teardown内部的代码
    :param kwargs: 用于接受setup或teardown的数据
    :param api: 传入api对象
    :param mysql: 传入mysql对象
    :param type: 要执行的类型
    :return:
    """
    logger.info("开始执行{}初始化函数".format(type))
    hook_data = kwargs.get(type)
    logger.info("获取到的{}数据:{}".format(type,hook_data if hook_data else '为空,跳过该函数执行'))
    if hook_data:
        call_type = hook_data.get('type')   # 在获取字典中type的值
        if 'api' == call_type:
            api_info = hook_data.get('data')
            logger.debug("执行的{}接口数据为:{}".format(type,api_info))
            api.request_api(api_info)
        if 'db' == call_type:
            sqls = hook_data.get('sqls')
            mysql.execute(sqls)
            if type == 'teardown':
                mysql.close()


# 作用: 将所有yml文件都读取数据转化为Python对象，放在一个总列表中.
def generate_tests(case_path,debug_case=None):
    case_path_list = _get_case_path_list(case_path)     # 所有yml文件的列表
    cases_list = []
    for test_data_path in case_path_list:
        if debug_case is None or len(debug_case) == 0:
            case = read_yaml_file(test_data_path,case_path)      # 将该yml文件的数据转化为python对象
            cases_list.extend(case)
            continue

        # 判断debug_case存在测试用例的情况
        if debug_case in test_data_path:
            case = read_yaml_file(test_data_path,case_path)      # 将该yml文件的数据转化为python对象
            cases_list.extend(case)
            break

    logger.debug("最终返回的用例列表为:{}".format(cases_list))
    return cases_list


# 搜集data目录下所有的yml文件，yml就是测试用例数据
def _get_case_path_list(base_dir):
    """
    返回目录下所有yml文件
    :param base_dir: 存放yml文件的路径
    :return:
    """
    case_path_list = []
    data_dir = get_path(base_dir)   # 读取测试数据目录

    # 先判断是否是目录
    if os.path.isdir(data_dir):
        for path in pathlib.Path(data_dir).glob("**/*.yml"):
            abs_path = str(path.absolute())
            print("abs_path:",abs_path)
            case_path_list.append(abs_path)
    if os.path.isfile(data_dir):
        print("=========")
        case_path_list.append(data_dir)

    if not case_path_list:
        logger.warning("没有匹配的接口测试用例:路径[{}]".format(data_dir))

    return case_path_list


def cases_list(data_type):
    """
    读取data下的所有测试数据
    :return:
    """
    # 判断是否要执行单个文件，若执行，将此文件名传入generate_tests()函数
    debug_case = cache.get('debug_case')
    if data_type == 'api_data':
        if debug_case:
            logger.info("获取到的调试用例为:{}".format(debug_case))
            return generate_tests('api_data', debug_case)
        return generate_tests('api_data')
    else:
        if debug_case:
            logger.info("获取到的调试用例为:{}".format(debug_case))
            return generate_tests('web_data', debug_case)
        return generate_tests('web_data')


# 从json结果中提取键名所对应的值
def get_val(actual_result,keys):
    key_list = str(keys).split('.')     # 将传入的键名按照.拆分放在列表中，比如：['response','list[0]','userName']
    for index,vls in enumerate(key_list):
        # 判断vls是否包含了[] ,代表它是一个列表。
        if vls.find('[') != -1:
            start = vls.find('[')       # 返回[在字符串中的索引
            end = vls.find(']')
            index_value = int(vls[start+1:end])     # 返回[]里的数字
            logger.info("带有列表的键名:{},索引值:{}".format(vls[0:start],index_value))
            actual_result = actual_result.get(vls[0:start])[index_value]
            continue
        actual_result = actual_result.get(vls)      # 获取字典中的值

    return actual_result


# 向缓存中保存数据
def set_data(cache_status,test_case,response_result=None,cache_key=None,aliase=None):
    """
    向内存保存要缓存的数据
    :param cache_status: 缓存的情况，对应is_cache中的数据
    :param test_case: 要传入的测试用例
    :param response_result: 响应结果
    :param cache_key: 要缓存的键明
    :return:
    """
    # 1. 缓存输入:
    if cache_status == 1:
        cache_req_params(test_case)
        return None
    # 2. 缓存输出
    if cache_status == 2:
        cache_response_data(response_result,cache_key,aliase)
        return None
    # 3.缓存输入和输出
    if cache_status == 3:
        cache_req_params(test_case)
        cache_response_data(response_result, cache_key,aliase)
        return None


# 缓存请求参数
def cache_req_params(test_case):
    dic_data = test_case.get('data').get('params')
    if not isinstance(dic_data,dict):
        raise TypeError("输入错误的数据类型，要求必须是字典")
    for key,value in dic_data.items():
        cache.set(key,value)
    return None


# 缓存响应数据
def cache_response_data(actual_result,cache_key=None,aliase=None):
    # 如果setargs没有值 或者等于None
    if cache_key is None:
        cache.set('response',actual_result.get('response'))
    elif isinstance(cache_key,list):        # 判断setargs传入的是列表
        for x in cache_key:
            cache_args(actual_result,x,aliase)
    else:
        cache_args(actual_result,cache_key,aliase)



# 判断期望结果值是否包含{{}} ，传入的值可能是各种数据类型。
def is_cache_value(value):
    """
    根据value的数据类型调用不同函数
    :param value:
    :return:
    """
    # 1. 判断是否是字符串
    if isinstance(value,str):
        return is_contains_brace(value)
    # 2. 判断是否是列表
    if isinstance(value,list):
        return list_is_contains_brace(value)

    # 3. 判断是否是字典
    if isinstance(value,dict):
        return dict_is_contains_brace(value)

    return value


# 判断期望结果是否包含{{}} :"{{userName}}"
def is_contains_brace(value):
    """
    返回"{{userName}}" 这个里面的值
    :param value:
    :return:
    """
    start_index = value.find("{{")  # 若包含{{ ,返回的是>=0的数 ,不包含返回的是-1
    end_index = value.find("}}")
    if start_index >=0 and end_index >= 0:
        vls = cache.get(value[start_index+2:end_index])
        logger.info("需要从缓存提取，键名:{},值:{}".format(value[start_index+2:end_index],vls))
        return vls
    return value


# 判断传入的值是一个列表
def list_is_contains_brace(vls):
    new_list = []
    # 循环列表中的每个值
    for x in vls:
        if isinstance(x,str):       # 列表里的值是字符串
            new_list.append(is_contains_brace(x))
            continue
        if isinstance(x,list):
            new_list.append(list_is_contains_brace(x))
            continue
        if isinstance(x,dict):
            new_list.append(dict_is_contains_brace(x))
            continue
        new_list.append(x)
    return new_list


# 判断传入的值是否是一个字典
def dict_is_contains_brace(vls):
    new_dict = {}
    for k,v in vls.items():
        if isinstance(v,str):
            new_dict[k] = is_contains_brace(v)
            continue
        if isinstance(v,list):
            new_dict[k] = list_is_contains_brace(v)
            continue
        if isinstance(v,dict):
            new_dict[k] = dict_is_contains_brace(v)

        new_dict[k] =v
    return new_dict


# 缓存列表里的参数 ：response[1].id == [response[1],id]
def cache_args(actual_result,keys,aliase=None):
    key_list = str(keys).split('.')  # 将传入的键名按照.拆分放在列表中，比如：['response','list[0]','userName']
    for index, vls in enumerate(key_list):
        # 判断vls是否包含了[] ,代表它是一个列表。
        if vls.find('[') != -1:
            start = vls.find('[')  # 返回[在字符串中的索引
            end = vls.find(']')
            index_value = int(vls[start + 1:end])  # 返回[]里的数字
            logger.info("带有列表的键名:{},索引值:{}".format(vls[0:start], index_value))
            actual_result = actual_result.get(vls[0:start])[index_value]
            continue
        actual_result = actual_result.get(vls)  # 获取字典中的值

        if index == len(key_list) -1:
            if aliase is None:
                logger.info("要缓存的键值对:{}:{}".format(vls,actual_result))
                cache.set(vls,actual_result)
            else:
                new_key = aliase.get(vls)       # 获取键多对应的别名
                logger.info("要缓存的键值对:{}:{}".format(new_key, actual_result))
                cache.set(new_key, actual_result)


# 获取真实的结果
def get_real_result(keyword,result):
    logger.debug("从yml用例传入的实际结果为:{}".format(result))
    if result.endswith(']'):
        start = result.rfind('[')
        end = result.rfind(']')
        index = result[start + 1:end]
        locator = result[0:start]  # 获取定位元素
        actual_result = keyword.text(eval(locator), int(index))
    else:
        actual_result = keyword.text(eval(result.rstrip()))  # 实际结果
    logger.info("获取到的实际结果为:{}".format(actual_result))
    return actual_result


# 从测试数据中提取测试结果
def from_assert_data_get_result(test_point,keyword):
    assert_data = []
    comp = "=="

    for comp in str(test_point).split(" ")[1:]:       # 取出中间的比较符，其它不能加空格
        logger.debug("从断言数据中获取的比较符为:{}".format(comp))

        if comp == '==':
            assert_data = str(test_point).split("==")  # 从断言数据中开始分割
            break
        elif comp == 'in':
            assert_data = str(test_point).split('in')
            break
        elif comp == 'Rin':
            assert_data = str(test_point).split('Rin')
            break
        else:
            continue
    else:
        logger.error("传入断言类型不支持，请联系管理员")
        sys.exit(1)

    res1 = assert_data[1].lstrip()  # 提取预期结果，去除空格
    expect_result = is_cache_value(res1)  # 提取是否有变量

    res2 = assert_data[0].rstrip()  # 提取实际结果 ，去除空格
    actual_result = get_real_result(keyword, res2)  # 获取真实结果
    return expect_result,actual_result,comp


# 进行断言编写
def assert_tests(test_point,keyword):
    import pytest
    import traceback

    # 获取期望结果，实际结果 ，以及断言符号
    expect_result, actual_result, comp = from_assert_data_get_result(test_point,keyword)
    try:
        if comp == '=':
            assert expect_result == actual_result
            logger.info("断言:{},预期结果:{},实际结果:{},比较结果:{}".
                         format(expect_result, expect_result, actual_result, expect_result == actual_result))
        elif comp == 'in':
            assert actual_result in expect_result
            logger.info("断言:{},预期结果:{},实际结果:{},比较结果:{}".
                         format(expect_result, expect_result, actual_result, actual_result in expect_result))
        elif comp == 'Rin':
            assert expect_result in actual_result
            logger.info("断言:{},预期结果:{},实际结果:{},比较结果:{}".
                         format(expect_result, expect_result, actual_result, expect_result in actual_result))
        else:
            assert True
    except AssertionError as e:
        logger.error("执行测试用例失败!,断言测试点为:{},预期结果:{},实际结果:{}".
                     format(test_point, expect_result, actual_result))
        cache.set('case_result',0)
        pytest.fail(str(e))

    except Exception:
        cache.set('case_result',2)
        logger.exception("执行测试用例异常!,代码报错,错误日志:{}".format(traceback.format_exc()))
        pytest.raises(Exception)


if __name__ == '__main__':
    print(generate_tests('api_data'))